using System;
using System.Collections.Generic;

namespace VirtualRink.Library
{
	public class Scheduler
	{
		private ScheduleParameters parameters = null;
		private League league;
		private IList<Team> allTeams;
		private List<int> availableDays = null;
		Season season;
		Random random;
        IRepository repository;

		public Scheduler(League league, Season season, ScheduleParameters scheduleParameters, IRepository repository)
		{
            this.repository = repository;
			this.random = new Random();
			this.league = league;
			this.season = season;
			this.parameters = scheduleParameters;

			ValidateScheduler();

            allTeams = this.repository.GetTeamsByLeague(league.Id);
		}

		public List<int> AvailableDays
		{
			get
			{
				if (availableDays == null)
				{
					availableDays = this.GetAvailableDays();
				}
				return availableDays;
			}
		}


		public List<Game> GenerateSchedule()
		{
			List<Game> teamGames = null;
			List<Game> newGames = new List<Game>();

			foreach (Team team in allTeams)
			{
				teamGames = this.GetTeamGames(team, newGames);
				newGames.AddRange(teamGames);
			}

			List<Game> finalGames = new List<Game>();
			Game game;
			int i;
			while (newGames.Count > 0)
			{
				i = this.random.Next(0, newGames.Count - 1);
				game = newGames[i];
				newGames.Remove(game);

				game.GameDate = season.StartDate.AddDays(
					this.GetBestAvailableGameDay(
					finalGames, game.HomeTeam, game.VisitingTeam));
                game.Season = this.season;
                game.League = this.league;
				finalGames.Add(game);
                this.repository.Context.Games.InsertOnSubmit(game);
			}

			return finalGames;

		}

		private void ValidateScheduler()
		{
			if (parameters.ConferenceGames % 2 > 0)
				throw new SchedulingException("Scheduling parameters are invalid.  The number of conference games between teams must be an even number.");

			if (parameters.DivisionGames % 2 > 0)
				throw new SchedulingException("Scheduling parameters are invalid.  The number of division games between teams must be an even number.");

		}

		private List<Game> GetGamesForDate(DateTime gameDate, List<Game> allGames)
		{
			List<Game> games = new List<Game>();
			foreach (Game g in allGames)
			{
				if (g.GameDate == gameDate)
					games.Add(g);
			}
			return games;
		}

		private int GetBestAvailableGameDay(List<Game> games, Team homeTeam, Team awayTeam)
		{
			if (games.Count == 0)
			{
				return parameters.StartDay;
			}
			List<Game> gamesOnDate = null;
			int d = int.MinValue;
			for (int i = 0; i < this.AvailableDays.Count; i++)
			{
				gamesOnDate = this.GetGamesForDate(season.StartDate.AddDays(AvailableDays[i]), games);
				if (gamesOnDate.Count == 0)
				{
					d = AvailableDays[i];
					break;
				}
				else
				{
					bool found = false;
					foreach (Game g in gamesOnDate)
					{
						if (g.HomeTeam.Name == homeTeam.Name || g.HomeTeam.Name == awayTeam.Name
							|| g.VisitingTeam.Name == homeTeam.Name || g.VisitingTeam.Name == awayTeam.Name)
						{
							found = true;
							break;
						}
					}

					if (!found)
					{
						d = AvailableDays[i];
						break;
					}
				}
			}

			if (d == int.MinValue || d == int.MaxValue)
				throw new InvalidOperationException("No suitable date could be found.");

			return d;
		}

		private List<Game> GetTeamGames(Team team, List<Game> scheduledGames)
		{
			int max = 0;
			List<Game> newGames = new List<Game>();

			foreach (Team opponent in allTeams)
			{
				if (opponent != team)
				{
					int homeGames = GetScheduledHomeGamesAgainstOpponent(team, opponent, scheduledGames);
					int awayGames = GetScheduledAwayGamesAgainstOpponent(team, opponent, scheduledGames);

					if (opponent.Division == team.Division)
					{
						//schedule a division game
						max = parameters.DivisionGames / 2;
					}
					else if (opponent.Division.Conference == team.Division.Conference)
					{
						//schedule an intra-conference game
						max = parameters.ConferenceGames / 2;
					}
					else
					{
						//schedule an inter-conference game
						max = parameters.LeagueGames / 2;
					}

					if (max == 0)
					{
						if (homeGames + awayGames == 0)
						{
							int result = random.Next(1, 100);
							if (result > 50)
							{
								Game newGame = new Game();
								newGame.GameDate = DateTime.MinValue;
								newGame.Season = season;
								season.Games.Add(newGame);
								newGame.HomeTeam = team;
								newGame.VisitingTeam = opponent;
								newGames.Add(newGame);
								homeGames += 1;
							}
							else
							{
								Game newGame = new Game();
								newGame.GameDate = DateTime.MinValue;
								newGame.Season = season;
								season.Games.Add(newGame);
								newGame.HomeTeam = opponent;
								newGame.VisitingTeam = team;
								newGames.Add(newGame);
								awayGames += 1;
							}
						}
					}
					else
					{
						while (homeGames < max)
						{
							Game newGame = new Game();
							newGame.GameDate = DateTime.MinValue;
							newGame.Season = season;
							season.Games.Add(newGame);
							newGame.HomeTeam = team;
							newGame.VisitingTeam = opponent;
							newGames.Add(newGame);
							homeGames += 1;
						}

						while (awayGames < max)
						{
							Game newGame = new Game();
							newGame.GameDate = DateTime.MinValue;
							newGame.Season = season;
							season.Games.Add(newGame);
							newGame.HomeTeam = opponent;
							newGame.VisitingTeam = team;
							newGames.Add(newGame);
							awayGames += 1;
						}
					}
				}
			}

			return newGames;
		}

		private int GetScheduledHomeGamesAgainstOpponent(Team team, Team opponent, List<Game> games)
		{
			int count = 0;
			foreach (Game game in games)
			{
				if (game.HomeTeam == team && game.VisitingTeam == opponent)
					count += 1;
			}
			return count;
		}

		private int GetScheduledAwayGamesAgainstOpponent(Team team, Team opponent, List<Game> games)
		{
			int count = 0;
			foreach (Game game in games)
			{
				if (game.VisitingTeam == team && game.HomeTeam == opponent)
					count += 1;
			}
			return count;
		}

		private List<int> GetAvailableDays()
		{
			List<int> days = new List<int>();
			days.Add(parameters.StartDay);
			for (int i = 1; i < 300; i++)
			{
				days.Add(days[i - 1] + parameters.MinimumDaysBetweenGames);
			}
			return days;
		}


	}
}
